package com.glodon.paas.job.agent.util;

import static org.apache.http.params.HttpConnectionParams.*;
import static org.apache.http.params.HttpProtocolParams.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.glodon.paas.job.JobConstants;
import com.glodon.paas.job.model.Task;

public class StoreUtil {
    private static final int MAX_RETRY_COUNT = 3;
    private static final Logger logger = LoggerFactory.getLogger(StoreUtil.class);

    public static File download(String url, File file, Task task) throws IOException {
        int retryCount = 0;
        while (true) {
            try {
                return downloadFile(url, file, task);
            } catch (SocketTimeoutException e) {
                // download failure, retry
                retryCount++;
                logger.warn("download failure, retry count [{}]", retryCount, e);
                if (retryCount > MAX_RETRY_COUNT)
                    throw e;
            }
        }
    }

    private static File downloadFile(String url, File folder, Task task) throws IOException, ClientProtocolException {
        logger.info("download file, url: {}, to path: {}", url, folder.getAbsolutePath());
        FileUtils.forceMkdir(folder);
        File file = null;
        HttpClient client = createHttpClient();
        HttpGet get = new HttpGet(url);
        get.setHeader("Content-Type", "text/html; charset=UTF-8");
        HttpResponse resp = client.execute(get);
        Header[] contentDisposition = resp.getHeaders("Content-Disposition");
        if (contentDisposition.length > 0) {
            String value = contentDisposition[0].getValue();
            String prefix = Long.toString(System.currentTimeMillis(), 10 + 26);
            String filename = StringUtils.substringBetween(value, "filename=\"", "\"");
            addFilename(task, filename);
            filename = URLDecoder.decode(filename, "UTF-8");
            String suffix = getSuffix(filename);
            file = new File(folder, prefix + suffix);
        }
        if (!isSuccess(resp))
            throw new IOException("download failure, url = " + url + ", error: " + getMessage(resp));
        Header[] pkgHeaders = resp.getHeaders("zip");
        boolean isPkg = pkgHeaders != null && pkgHeaders.length > 0 && "true".equals(pkgHeaders[0].getValue());
        File target;
        if (isPkg) {
            target = new File(file.getParentFile(), file.getName() + ".package");
            save(resp, target);
            unzip(target, file);
            if(!FileUtils.deleteQuietly(target))
                logger.info("can't delete package file: {}", target);
        } else {
            target = file;
            save(resp, target);
        }
        logger.info("download response: {}", resp);
        return file;
    }

    private static void addFilename(Task task, String filename) {
        // TODO support process multiple file
        Object filenames = task.getValue("filename");
        if(filenames != null)
            logger.warn("1 task download multiple files");
        task.addValue(JobConstants.ORIGIN_FILENAME, filename);
		task.setOriginFilename(filename);
    }

    private static String getSuffix(String filename) {
        String suffix = "";
        if (filename != null) {
            int indexOf = filename.lastIndexOf('.');
            if (indexOf > -1)
                suffix = filename.substring(indexOf);
        }
        return suffix;
    }

    private static void unzip(File target, File base) throws ZipException, IOException {
        ZipFile zipFile = new ZipFile(target);
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            String name = zipEntry.getName();
            if (!zipEntry.isDirectory()) {
                File file = new File(base, name);
                FileUtils.forceMkdir(file.getParentFile());
                FileOutputStream fos = new FileOutputStream(file);
                InputStream is = zipFile.getInputStream(zipEntry);
                IOUtils.copy(is, fos);
                is.close();
                fos.close();
            }
        }
        zipFile.close();
    }

    private static void save(HttpResponse resp, File target) throws IOException, FileNotFoundException {
        InputStream is = resp.getEntity().getContent();
        File parent = target.getParentFile();
        if (!parent.exists())
            parent.mkdirs();
        FileOutputStream os = new FileOutputStream(target);
        IOUtils.copy(is, os);
        os.close();
        is.close();
    }

    public static void upload(String url, File file) throws FileNotFoundException, IOException, ClientProtocolException {
        int retryCount = 0;
        while (true) {
            try {
                HttpResponse resp = uploadFile(url, file);
                if (!isSuccess(resp))
                    throw new IOException("upload failure: " + getMessage(resp));
                logger.info("upload response: {}", getMessage(resp));
                return;
            } catch (SocketTimeoutException e) {
                retryCount++;
                // retry upload
                logger.warn("upload failure, retry count [{}]", retryCount, e);
                if (retryCount > MAX_RETRY_COUNT)
                    throw e;
            }
        }
    }

    private static String getMessage(HttpResponse resp) throws IOException {
        HttpEntity entity = resp.getEntity();
        StringBuilder err = new StringBuilder();
        err.append(resp);
        if (entity != null) {
            InputStream is = entity.getContent();
            String s = IOUtils.toString(is);
            is.close();
            err.append('\n').append(s);
        }
        String errorMsg = err.toString();
        return errorMsg;
    }

    private static boolean isSuccess(HttpResponse resp) throws IOException {
        int statusCode = resp.getStatusLine().getStatusCode();
        return !(statusCode < 200 || statusCode > 299);
    }

    private static HttpResponse uploadFile(String url, File file) throws FileNotFoundException, IOException,
            ClientProtocolException {
        HttpClient client = createHttpClient();
        logger.info("update file, file: {}, url: {}", file.getAbsolutePath(), url);
        HttpPost post = new HttpPost(url);
        post.addHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
        post.setEntity(new InputStreamEntity(new FileInputStream(file), file.length()));
        return client.execute(post);
    }

    private static HttpClient createHttpClient() {
        HttpClient client = new DefaultHttpClient();
        HttpParams params = client.getParams();
        setSoTimeout(params, 10000);
        setConnectionTimeout(params, 10000);
        setContentCharset(params, "UTF-8");
        setHttpElementCharset(params, "UTF-8");
        return client;
    }
}
